/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> uls_conf.c </file>
  <brief>
    Loading the lexical spec file (*.ulc) of ULS.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, April 2011.
  </author>
*/

#include "common.h"
#include "uls_conf.h"
#include "uls_lex.h"
#include "uls_log.h"

#define LXM_LINES_DELTA 128

static int
check_keyw_str(int lno, uls_lex_t* uls, uls_char_t* str, uls_char_t* buf, int* l_buf)
{
	uls_char_t *ch_ctx = uls->i_str.ch_context, ch;
	uls_char_t* ptr;
	int  i, len, rc;
	int  n_ch_quotes, n_ch_comms, n_ch_non_idstr;
	int  n_lfs, n_tabs;
	istr_commtype_t *cmt;
	istr_quotetype_t *qmt;
	uls_uch_t uch;

	if (*str == '\0') {
		err_log("#%d: contains no keyword", lno);
		return -1;
	}

	// assert: sizeof(buf) < ULS_LEXSTR_MAXSIZ
	n_ch_quotes = n_ch_comms = n_ch_non_idstr = 0;
	n_lfs = n_tabs = 0;

	for (len=0, ptr=str; (ch=*ptr)!='\0'; ) {
		if (len > ULS_LEXSTR_MAXSIZ) {
			err_log("#%d: Too long keyword '%s'", lno, str);
			return -1;
		}

		if (ch == '\\') {
			++ptr;
			uch = uls_get_escape_char((char **) &ptr);
			len += uls_encode_utf8(uch, buf+len);
		} else {
			++ptr;
			buf[len++] = ch;
		}
	}

	buf[len] = '\0';
	if (l_buf != NULL) *l_buf = len;

	ptr = buf;
	if ((rc=uls_is_char_idfirst(uls, ptr)) > 0) {
		do {
			ptr += rc;
		} while ((rc=uls_is_char_id(uls, ptr)) > 0);

		if (*ptr != '\0') {
			err_log("#%d: The keyword that started with an id-char shouldn't contain non-id-char", lno);
			return -1;
		}

		n_ch_non_idstr = 0;

	} else {
		do {
			if (uls_is_char_idfirst(uls, ptr) > 1 ||
				uls_is_char_id(uls, ptr) > 1) {
				err_log("#%d: keyword can't contain unicode id-str", lno);
				return -1;
			}

			if ((rc=uls_decode_utf8(ptr, &uch)) <= 0) {
				uch = *ptr;
				rc = 1;
			}

			if ((ch=*ptr) == ' ') {
				err_log("#%d: contains illegal char in keyword", lno);
				return -1;
			}

			if (ch == '\n') ++n_lfs;
			else if (ch == '\t') ++n_tabs;

			if (ch_ctx[ch] & ULS_CH_QUOTE) ++n_ch_quotes;
			if (ch_ctx[ch] & ULS_CH_COMM) ++n_ch_comms;

		} while (*(ptr += rc) != '\0');

		n_ch_non_idstr = 1;
	}

	if (len > 1 && (n_lfs > 0 || n_tabs > 0)) {
		err_log("#%d: contains illegal chars in keyword\n", lno);
		return -1;
	}

	if (n_ch_comms > 0) {
		for (i=0; i<uls->i_str.n_commtypes; i++) {
			cmt = uls->i_str.commtypes + i;
			if (strstr((char *) buf, (char *) cmt->start_mark) != NULL) {
				err_log("#%d: keyword has the comment-string '%s'", lno, cmt->start_mark);
				return -1;
			}
		}
	}

	if (n_ch_quotes > 0) {
		for (i=0; i<uls->i_str.n_quotetypes; i++) {
			qmt = uls->i_str.quotetypes + i;
			if (strstr((char *) buf, (char *) qmt->start_mark) != NULL) {
				err_log("#%d: the keyword '%s' has a substring of the quote-string '%s'",
					lno, buf, qmt->start_mark);
				return -1;
			}
		}
	}

	if (n_ch_non_idstr == 0) {
		rc = ULS_KEYW_TYPE_IDSTR;
	} else if (len == 1) {
		rc = ULS_KEYW_TYPE_1CHAR;
	} else {
		rc = ULS_KEYW_TYPE_INTRINSIC;
	}

	return rc;
}

static uls_tokdef_t*
__find_lxm_by_tokid(uls_lex_t* uls, int i1, int i2p1, int t)
{
	uls_tokdef_t  *e;
	int i;

	if (i1 < 0) i1 = 0;
	if (i2p1 < 0) i2p1 = uls->n_lxm_array;
	else if (i2p1 > uls->n_lxm_array)
		i2p1 = uls->n_lxm_array;
	if (i1 >= i2p1) return NULL;

	for (i=i1; i<i2p1; i++) {
		e = uls->lxm_array[i];
		if (e->tok_id == t) {
			return e;
		}
	}

	return NULL;
}

static uls_tokdef_t*
__find_lxm(uls_lex_t* uls, char* name, uls_char_t* kwrd, int i1, int i2p1)
{
	uls_tokdef_t *e, *e0=NULL;
	int i;

	if (i1 < 0) i1 = 0;
	if (i2p1 < 0) i2p1 = uls->n_lxm_array;
	else if (i2p1 > uls->n_lxm_array)
		i2p1 = uls->n_lxm_array;
	if (i1 >= i2p1) return NULL;

	if (name != NULL) {
		for (i=i1; i<i2p1; i++) {
			e = uls->lxm_array[i];
			if (uls_streql(e->name, name)) {
				e0 = e; break;
			}
		}
	} else if (kwrd != NULL) {
		for (i=i1; i<i2p1; i++) {
			e = uls->lxm_array[i];
			if (uls_streql(e->keyword, kwrd)) {
				e0 = e; break;
			}
		}
	}

	return e0;
}

static int commtype_by_length(const void* a, const void* b)
{
	istr_commtype_t *e1 = (istr_commtype_t *) a;
	istr_commtype_t *e2 = (istr_commtype_t *) b;
	int stat;

	if (e1->len_start_mark < e2->len_start_mark) stat = 1;
	else if (e1->len_start_mark > e2->len_start_mark) stat = -1;
	else stat = 0;

	return stat;
}

static int quotetype_by_length(const void* a, const void* b)
{
	istr_quotetype_t *e1 = (istr_quotetype_t *) a;
	istr_quotetype_t *e2 = (istr_quotetype_t *) b;
	int stat;

	if (e1->len_start_mark < e2->len_start_mark) stat = 1;
	else if (e1->len_start_mark > e2->len_start_mark) stat = -1;
	else stat = 0;

	return stat;
}

static int comp_intrinsic_id_by_length(const void* a, const void* b)
{
	uls_tokdef_t *e1 = *((uls_tokdef_t **) a);
	uls_tokdef_t *e2 = *((uls_tokdef_t **) b);
	int stat;

	if (e1->l_keyword < e2->l_keyword) stat = 1;
	else if (e1->l_keyword > e2->l_keyword) stat = -1;
	else stat = 0;

	return stat;
}

static int
classify_char_group(uls_lex_t* uls, ulc_header_t* uls_conf)
{
	uls_char_t *tbl = uls->i_str.ch_context;
	istr_commtype_t *cmt;
	istr_quotetype_t *qmt;
	int  ch, i;

	for (i=0; i<uls->i_str.n_commtypes; i++) {
		cmt = uls->i_str.commtypes + i;
		ch = cmt->start_mark[0];
		tbl[ch] |= ULS_CH_COMM;
	}

	for (i=0; i<uls->i_str.n_quotetypes; i++) {
		qmt = uls->i_str.quotetypes + i;
		ch = qmt->start_mark[0];
		tbl[ch] |= ULS_CH_QUOTE;
	}

	/* '\0' : An exceptional char */
	if (tbl['\0'] != 0) {
		err_log("The null char can't be used as other usage");
		tbl['\0'] = 0;
	}

	/* ' ' : ~VISIBLE */
	if (tbl[' '] != 0) {
		err_log("The space char can't be used as other usage");
		tbl[' '] = 0;
	}

	if (tbl['\n'] != 0) {
		err_log("Here, '\\n' is set to non-trivial char!");
		return -1;
	}

	if (tbl['\t'] != 0) {
		err_log("Here, '\\t' is set to non-trivial char!");
		return -1;
	}

	if (uls->toknum_EOI >= 0 && uls->toknum_EOI < ULS_SYNTAX_TABLE_SIZE)
		tbl[uls->toknum_EOI] &= ~ULS_CH_1;

	if (uls->toknum_EOF >= 0 && uls->toknum_EOF < ULS_SYNTAX_TABLE_SIZE)
		tbl[uls->toknum_EOF] &= ~ULS_CH_1;

	if (uls->toknum_ID >= 0 && uls->toknum_ID < ULS_SYNTAX_TABLE_SIZE)
		tbl[uls->toknum_ID] &= ~ULS_CH_1;

	if (uls->toknum_NUMBER >= 0 && uls->toknum_NUMBER < ULS_SYNTAX_TABLE_SIZE)
		tbl[uls->toknum_NUMBER] &= ~ULS_CH_1;

	if (uls->toknum_ERR >= 0 && uls->toknum_ERR < ULS_SYNTAX_TABLE_SIZE)
		tbl[uls->toknum_ERR] &= ~ULS_CH_1;

	return 0;
}

int
lexi_order_ascii(uls_char_t* wrd1, int l_wrd1, uls_char_t* wrd2, int l_wrd2)
{
	// return -1 if wrd1 < wrd2
	// return  1 if wrd1 > wrd2
	// return  0 if wrd1 == wrd2
	return strcmp((const char*) wrd1, (const char*) wrd2);
}

int
lexi_order_ascii_case(uls_char_t* wrd1, int l_wrd1, uls_char_t* wrd2, int l_wrd2)
{
	return uls_strcasecmp((const char *) wrd1, (const char *) wrd2);
}

static int
add_id_charset(unsigned int* ary_1, unsigned int* ary_2, int n_ary,
	unsigned int i1, unsigned int i2)
{
	int k;

	if (i1 > i2) return 0;

	if ((k=n_ary) >= ULS_N_CHARSET_RANGES) {
		return -1;
	}

	ary_1[k] = i1;
	ary_2[k] = i2;

	return 1;
}

int
uls_is_char_idfirst(uls_lex_t* uls, uls_char_t* lptr)
{
	int n_bytes, i;
	uls_uch_t uch;

	if ((n_bytes=uls_decode_utf8(lptr, &uch)) <= 0)
		n_bytes = 1;

	// assert: n_bytes == 1, 2, 3, 4
	if (n_bytes > 1) { // UTF-8
		for (i=0; i<uls->n_idfirst_charsets; i++) {
			if (uch >= uls->idfirst_charset_1[i] &&
				uch <= uls->idfirst_charset_2[i])
				return n_bytes;
		}
	} else if (uls->i_str.ch_context[*lptr] & ULS_CH_IDFIRST) {
		return 1;
	}

	return -n_bytes;
}

int
uls_is_char_id(uls_lex_t* uls, uls_char_t* lptr)
{
	int n_bytes, i;
	uls_uch_t uch;

	if ((n_bytes=uls_decode_utf8(lptr, &uch)) <= 0)
		n_bytes = 1;

	// assert: n_bytes == 1, 2, 3, 4
	if (n_bytes > 1) { // UTF-8
		for (i=0; i<uls->n_id_charsets; i++) {
			if (uch >= uls->id_charset_1[i] &&
				uch <= uls->id_charset_2[i])
				return n_bytes;
		}
	} else if (uls->i_str.ch_context[*lptr] & ULS_CH_ID) {
		return 1;
	}

	return -n_bytes;
}

static int
cnst_suffixes_by_len(const void* a, const void* b)
{
	const char *e1 = *((const char **) a);
	const char *e2 = *((const char **) b);
	int len1, len2, stat;

	len1 = strlen(e1);
	len2 = strlen(e2);

	if (len1  < len2) stat = 1;
	else if (len1  > len2) stat = -1;
	else stat = 0;

	return stat;
}

static int
check_validity_of_cnst_suffix(char **cnst_suffixes, int n_cnst_suffixes, char* suffixes_str)
{
	char *ptr, ch;
	int i, j;

	for (i=0; i<n_cnst_suffixes; i++) {
		ptr = cnst_suffixes[i];
		for (j=0; (ch=ptr[j]) != '\0'; j++) {
			if (!isgraph(ch) || isdigit(ch)) {
				err_log("An invalid constant suffix found. ignoring '%s', ...", ptr);
				cnst_suffixes[i] = NULL;
			}
		}

		if (j == 0) cnst_suffixes[i] = NULL;
	}

	for (i=0; i<n_cnst_suffixes; ) {
		if ((ptr=cnst_suffixes[i]) == NULL) {
			cnst_suffixes[i] = cnst_suffixes[--n_cnst_suffixes];
		} else {
			++i;
		}
	}

	qsort(cnst_suffixes, n_cnst_suffixes, sizeof(char*), cnst_suffixes_by_len);

	for (i=0; i<n_cnst_suffixes; i++) {
		ptr = cnst_suffixes[i];
		add_cnst_suffix(suffixes_str, ptr);
	}

	return n_cnst_suffixes;
}

static int
read_config_var(int lno, uls_lex_t* uls, char* lptr, ulc_header_t* hdr)
{
	uls_char_t  *tbl = uls->i_str.ch_context;
	char  *wrd[ULC_COLUMNS_MAXSIZ], *parm;
	istr_commtype_t *commtype;
	istr_quotetype_t *quotetype;
	uls_tokdef_t *e;
	int   i, k, len, n, i_parm, n_parms, ch, rc, tok_id;
	unsigned int i1, i2, i_tmp, j;

	n_parms = uls_get_column_words(lptr, ' ', wrd, ULC_COLUMNS_MAXSIZ);

	if (uls_streql(wrd[0], "CASE_SENSITIVE:")) {
		if (uls_streql(wrd[1], "false")) {
			uls->flags |= ULS_CASE_INSENSITIVE;
			uls->lxm_strcmp = lexi_order_ascii_case;
		} else if (uls_streql(wrd[1], "true")) {
			uls->flags &= ~ULS_CASE_INSENSITIVE;
			uls->lxm_strcmp = lexi_order_ascii;
		} else {
			err_log("#%d: Incorrect value of CASE_SENSITIVE. Specify it by true/false.", lno);
		}

	} else if (uls_streql(wrd[0], "COMMENT_MARKER:")) {
		if (uls->i_str.n_commtypes >= ULS_N_MAX_COMMTYPES) {
			err_log("#%d: Too many comment types", lno);
			return -1;
		}

		commtype = uls->i_str.commtypes + uls->i_str.n_commtypes;
		commtype->n_lfs = 0;

		if ((len=uls_strlen(wrd[1]))==0 || len > ULS_COMM_MARK_MAXSIZ ||
			!canbe_commtype_mark((uls_char_t *) wrd[1], commtype->start_mark, &commtype->len_start_mark, &n)) {
			err_log("#%d: Too short or long comment (start) marker, or not permitted chars in '%s'", lno, wrd[1]);
			return -1;
		}
		commtype->n_lfs += n;

		if ((len=uls_strlen(wrd[2]))==0 || len > ULS_COMM_MARK_MAXSIZ ||
			!canbe_commtype_mark((uls_char_t *) wrd[2], commtype->end_mark, &commtype->len_end_mark, &n)) {
			err_log("#%d: Too short or long comment (end) marker, or not permitted chars in '%s'", lno, wrd[2]);
			return -1;
		}
		commtype->n_lfs += n;

		++uls->i_str.n_commtypes;

	} else if (uls_streql(wrd[0], "QUOTE_MARKER:")) {
		if (uls->i_str.n_quotetypes >= ULS_N_MAX_QUOTETYPES) {
			err_log("#%d: Too many quote types", lno);
			return -1;
		}

		quotetype = uls->i_str.quotetypes + uls->i_str.n_quotetypes;
		quotetype->name[0] = '\0';

		if ((len=uls_strlen(wrd[1]))==0 || len > ULS_QUOTE_MARK_MAXSIZ ||
			!canbe_quotetype_mark((uls_char_t *) wrd[1], quotetype->start_mark, &quotetype->len_start_mark)) {
			err_log("#%d: Too short or long quote (start) marker, or not permitted chars in '%s'", lno, wrd[1]);
			return -1;
		}

		if ((len=uls_strlen(wrd[2]))==0) {
			uls_strcpy(quotetype->end_mark, quotetype->start_mark);
		} else if (len > ULS_QUOTE_MARK_MAXSIZ ||
			!canbe_quotetype_mark((uls_char_t *) wrd[2], quotetype->end_mark, &quotetype->len_end_mark)) {
			err_log("#%d: Too short or long quote (end) marker, or not permitted chars in '%s'", lno, wrd[2]);
			return -1;
		}

		i_parm =  3;
		parm = wrd[i_parm];

		if (uls_streql(parm, "verbatim")) {
			quotetype->literal_analyzer = verbatim_lit_analyzer;
			parm = wrd[++i_parm];
		} else {
			quotetype->literal_analyzer = default_lit_analyzer;
		}

		if (*parm == '\0') {
			tok_id = quotetype->start_mark[0];
		} else {
			tok_id = atoi(parm);

			if (!(tok_id>=0 && tok_id<ULS_SYNTAX_TABLE_SIZE) || !isgraph(tok_id)) {
				parm = wrd[++i_parm];

				if (*parm == '\0') {
					err_log("#%d: need tok-name for specifying quote-string '%s'",
						lno, quotetype->start_mark);
					return -1;
				}

				if (uls_strlen(parm) > ULS_LEXSTR_MAXSIZ) {
					err_log("#%d: Too long name for '%s'", lno, quotetype->start_mark);
					return -1;
				}
				uls_strcpy(quotetype->name, parm);
			}

		}

		if (find_quotetype_by_tokid(
			uls->i_str.quotetypes, uls->i_str.n_quotetypes, tok_id) != NULL) {
			err_log("#%d: tok-id %d already used", lno, tok_id);
			return -1;
		}

		quotetype->tok_id = tok_id;

		++uls->i_str.n_quotetypes;

	} else if (uls_streql(wrd[0], "ID_FIRST_CHARS:")) {
		for (k=1; k<n_parms; k++) {
			parm = wrd[k];

			/* TESTCASE: $_. 120 0x221-226 */
			if (isdigit(*parm)) {
				if (get_range_atou(&parm, &i1, &i2) <= 0) {
					err_log("#%d: improper uls_char_t-range specified: '%s'.", lno, parm);
					return -1;
				}

				if (i1 < 0x80) {
					i_tmp = MIN(i2+1, 0x80);
					for (j=i1; j<i_tmp; j++) {
						tbl[j] |= ULS_CH_IDFIRST;
					}
					i1 = i_tmp;
				}

				if ((rc=add_id_charset(uls->idfirst_charset_1, uls->idfirst_charset_2,
						uls->n_idfirst_charsets, i1, i2)) < 0) {
						err_log("#%d: fail to add id-charset 0x%04x-%04x", lno, i1, i2);
						return -1;	
				} else if (rc > 0) {
					++uls->n_idfirst_charsets;
				}

			} else {
				for (i=0; (ch=parm[i])!='\0'; i++) {
					tbl[ch] |= ULS_CH_IDFIRST;
				}
			}
		}

	} else if (uls_streql(wrd[0], "ID_CHARS:")) {
		for (k=1; k<n_parms; k++) {
			parm = wrd[k];

			/* TESTCASE: $_. 120, 0x221-226 */
			if (isdigit(*parm)) {
				if (get_range_atou(&parm, &i1, &i2)<=0) {
					err_log("#%d: improper uls_char_t-range specified: '%s'.\n", lno, parm);
					return -1;
				}

				if (i1 < 0x80) {
					i_tmp = MIN(i2+1, 0x80);
					for (j=i1; j<i_tmp; j++) {
						tbl[j] |= ULS_CH_ID;
					}
					i1 = i_tmp;
				}

				if ((rc=add_id_charset(uls->id_charset_1, uls->id_charset_2, uls->n_id_charsets, i1, i2)) < 0) {
					err_log("#%d: fail to add id-charset 0x%04x-%04x\n", lno, i1, i2);
					return -1;
				} else if (rc > 0) {
					++uls->n_id_charsets;
				}

			} else {
				for (i=0; (ch=parm[i])!='\0'; i++) {
					tbl[ch] |= ULS_CH_ID;
				}
			}
		}

	} else if (uls_streql(wrd[0], "RENAME:")) {
		if ( *(wrd[1]) == '\0' || *(wrd[2]) == '\0' ||
			uls_strlen(wrd[2]) > ULS_LEXSTR_MAXSIZ) {
			err_log("#%d: Invalid 'RENAME' line!", lno);
			return -1;
		}

		if ((e=__find_lxm(uls, wrd[1], NULL, 0, N_RESEVED_TOKS)) == NULL) {
			err_log("#%d: Invalid 'RENAME' for '%s'!", lno, wrd[1]);
			return -1;
		} else {
			uls_strcpy(e->name, wrd[2]);
		}

	} else if (uls_streql(wrd[0], "FILE_VERSION:")) {
		if (uls_version_pars_str(wrd[1], &hdr->filever) < 0) {
			return -1;
		}

	} else if (uls_streql(wrd[0], "VERSION:")) {
		if (uls_version_pars_str(wrd[1], &uls->spec_ver) < 0) {
			return -1;
		}

	} else if (uls_streql(wrd[0], "ICNST_SUFFIXES:")) {
		n_parms = check_validity_of_cnst_suffix(wrd+1, n_parms-1, uls->icnst_suffixes) + 1;

	} else if (uls_streql(wrd[0], "FCNST_SUFFIXES:")) {
		n_parms = check_validity_of_cnst_suffix(wrd+1, n_parms-1, uls->fcnst_suffixes) + 1;

	} else {
		err_log("%s: An unknown attribute in ULS-spec", wrd[0]);
		return -1;
	}

	return 0;
}

static int
ulc_read_header(uls_lex_t* uls, FILE* fin, ulc_header_t* hdr)
{
	char  linebuff[ULS_LINEBUFF_SIZ], *lptr;
	int   linelen, lno;
	char  ver_str[ULS_VERSION_STR_MAXLEN+1];

	/* Read Header upto '%%' */
	lno = 0;
	while (fgets(linebuff, ULS_LINEBUFF_SIZ, fin) != NULL) {
		++lno;

		if ((linelen=strlen(linebuff))>0 && linebuff[linelen-1]=='\n') {
			linebuff[--linelen] = '\0';
		}

		if (uls_strncmp(linebuff, "%%%%", 2)==0) {
			break;
		}

		lptr = skip_space_chars(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		if (read_config_var(lno, uls, lptr, hdr) < 0) {
			return -1;
		}
	}

	if (uls_version_cmp_code(&uls->config_filever, &hdr->filever) < 2) {
		uls_version_make_string(&hdr->filever, ver_str);
		err_log("unsupported version(ulc): %s", ver_str);
		return -1;
	}

	if (uls->lxm_strcmp == NULL)
		uls->lxm_strcmp = lexi_order_ascii;

	return lno;
}

static int
is_resevered_tok(uls_lex_t* uls, char* name)
{
	uls_tokdef_t *e;
	int i;

	for (i=0; i<N_RESEVED_TOKS; i++) {
		e = uls->lxm_array[i];
		if (uls_streql(e->name, name)) {
			return i;
		}
	}

	return -1;
}

static int
check_rsvd_toks(uls_lex_t* uls)
{
	uls_tokdef_t *e0, *e;
	int i, j, stat=0;

	for (i=0; i<N_RESEVED_TOKS; i++) {
		e0 = uls->lxm_array[i];

		for (j=i+1; j<N_RESEVED_TOKS; j++) {
			e = uls->lxm_array[j];

			if (uls_streql(e0->name, e->name)) {
				err_log("The name of %s is already used by %s\n", e->name, e0->name);
				stat = -1;
				break;
			}

			if (e0->tok_id == e->tok_id) {
				err_log("The token id %d of %s is already used by %s\n",
				e->tok_id, e->name, e0->name);
				stat = -1;
				break;
			}
		}
	}

	return stat;
}

static int
canbe_tokname(const uls_char_t *str)
{
	int i, val;
	uls_char_t ch;

	for (i=0; (ch=str[i])!='\0'; i++) {
		if (i > 0) {
			val = isalnum(ch) || (ch == '_');
		} else {
			val = isalpha(ch) || (ch == '_');
		}
		if (val == 0) return 0;
	}

	if (i > ULS_LEXSTR_MAXSIZ)
		return 0;

	return i;
}

static void
append_to_tokid_group(uls_tokdef_t *e_target, uls_tokdef_t *e_list)
{
	// assert: e_list != NULL;
	uls_tokdef_t **pp_e, *e;

	for (pp_e=&e_list->next; (e=*pp_e) != NULL; pp_e=&e->next)
		/* NOTHING */;

	e_target->next = NULL;
	*pp_e = e_target;
}

static uls_tokdef_t*
conf_proc_line(int lno, uls_lex_t* uls, char* lptr, ulc_header_t* hdr)
{
	uls_char_t *ch_ctx = uls->i_str.ch_context;
	char       *wrd[4], ch, ch_kwd;
	uls_char_t  wrdbuf1[ULS_LEXSTR_MAXSIZ+1];
	int  len_kwrd, tok_id, rsv_idx, keyw_type;
	istr_quotetype_t *q;
	uls_tokdef_t *e, *e_grp, *e2;
	uls_uch_t  uch;

	uls_get_column_words(lptr, ' ', wrd, 4);

/* token-name */
	if (canbe_tokname((uls_char_t *) wrd[0]) <= 0) {
		err_log("#%d: Nil-string or too long token constant '%s'", lno, wrd[0]);
		return NULL;
	}

	if ((rsv_idx=is_resevered_tok(uls, wrd[0])) >= 0) {
		if ((len_kwrd = uls_strlen(wrd[1])) > ULS_LEXSTR_MAXSIZ) {
			// The keywords for ID, NUMBER, .. are nonsense.
			len_kwrd = ULS_LEXSTR_MAXSIZ;
			wrd[1][len_kwrd] = '\0';
		}

		if ( *(wrd[2]) != '\0') {
			tok_id = atoi(wrd[2]);
			hdr->tok_id_seed = tok_id + 1;
		} else {
			tok_id = hdr->tok_id_seed++;
		}

		if ((e2=__find_lxm_by_tokid(uls, N_RESEVED_TOKS, -1, tok_id)) != NULL) {
			err_log("#%d: The token-id %d of %s is already used by %s.", lno, tok_id, wrd[0], e2->name);
			return NULL;
		}

		if (tok_id>=0 && tok_id < ULS_SYNTAX_TABLE_SIZE) {
			if ((ch_ctx[tok_id] & ULS_CH_TOKID_REMAPPED) == 0)
				ch_ctx[tok_id] &= ~ULS_CH_1;
		}

		e = uls->lxm_array[rsv_idx];
		uls_strcpy(e->keyword, wrd[1]);
		e->l_keyword = len_kwrd;
		e->tok_id = tok_id;
		e->kwrd_type = ULS_KEYW_TYPE_RESERVED;
		e->hlink = e->next = NULL;
		e->flags = ULS_TOKDEF_FL_RSVD | ULS_TOKDEF_FL_TOKID_LEADER;

		return e;
	}

/* keyword */
	if ((keyw_type = check_keyw_str(lno, uls, (uls_char_t *) wrd[1], wrdbuf1, &len_kwrd)) < 0) {
		err_log("#%d: Invalid keyword '%s'", lno, wrd[1]);
		return NULL;
	}

	if ((e2=__find_lxm(uls, NULL, wrdbuf1, N_RESEVED_TOKS, -1)) != NULL) {
		err_log("#%d: keyword '%s' conficts with '%s'", lno, wrd[1], e2->name);
		return NULL;
	}

	wrd[1] = (char *) wrdbuf1;

	e_grp = __find_lxm(uls, wrd[0], NULL, N_RESEVED_TOKS, -1);

/* token-id */
	if ( *(wrd[2]) != '\0') {
		tok_id = atoi(wrd[2]);

		if (e_grp != NULL) {
			if (e_grp->tok_id != tok_id) {
				err_log("#%d: %s(%d) conflicts with the previously defined '%s'.",
					lno, wrd[0], tok_id, e_grp->keyword);
				return NULL;
			}
		} else if ((e2=__find_lxm_by_tokid(uls, 0, -1, tok_id)) != NULL) {
			err_log("The token-id %d of %s is already used by %s.", tok_id, wrd[0], e2->name);
			return NULL;
		}

		hdr->tok_id_seed = tok_id + 1;

	} else {
		if (is_int_number(wrd[1])) {
			err_log("Illegal keyword: A pure number('%s') in the keyword column at #%d in the ulc-file.", wrd[1], lno);
			return NULL;
		}

		if (e_grp != NULL) {
			tok_id = e_grp->tok_id;
		} else {
			if (keyw_type == ULS_KEYW_TYPE_1CHAR) {
				tok_id = wrd[1][0];
			} else {
				tok_id = hdr->tok_id_seed++;
			}

			if ((e2=__find_lxm_by_tokid(uls, 0, -1, tok_id)) != NULL) {
				err_log("#%d: The token-id %d of %s is already used by %s.", lno, tok_id, wrd[0], e2->name);
				return NULL;
			}
		}
	}

/* check tok-id */
	if ((q=find_quotetype_by_tokid(
		uls->i_str.quotetypes, uls->i_str.n_quotetypes, tok_id)) != NULL) {
		err_log("#%d: %s(%d) conflicts the token previously defined.", lno, wrd[0], tok_id);
		return NULL;
	}

/* allocate the slot for new uls_tokdef_t */
	if (uls->n_lxm_array >= uls->siz_lxm_array) {
		uls->siz_lxm_array = uls->n_lxm_array + LXM_LINES_DELTA;
		uls->lxm_array = (uls_tokdef_t **) uls_mrealloc(uls->lxm_array,
			uls->siz_lxm_array * sizeof(uls_tokdef_t *));
	}

	e = uls->lxm_array[uls->n_lxm_array++] = 
		(uls_tokdef_t *) uls_malloc(sizeof(uls_tokdef_t));
	e->flags = 0;
	uls_strcpy(e->name, wrd[0]);
	e->hlink = e->next = NULL;

/* check keyword */
	e->kwrd_type = keyw_type;

	switch (e->kwrd_type) {
	case ULS_KEYW_TYPE_1CHAR:
		if (tok_id >= 0 && tok_id < ULS_SYNTAX_TABLE_SIZE) {
			if (uls_loglevel_isset(uls, ULS_LOG_NOTICE))
					err_log("Invalidate char-token, %d, ...", tok_id);
			if ((ch_ctx[tok_id] & ULS_CH_TOKID_REMAPPED) == 0)
				ch_ctx[tok_id] &= ~ULS_CH_1;
		}

		ch_kwd = wrd[1][0];
		len_kwrd = 1;

		if (ch_kwd == '\n') uls->flags |= ULS_WANT_LF_CHAR;
		else if (ch_kwd == '\t') uls->flags |= ULS_WANT_TAB_CHAR;

		ch_ctx[ch_kwd] |= ULS_CH_1;
		if (tok_id != ch_kwd) {
			ch_ctx[ch_kwd] |= ULS_CH_TOKID_REMAPPED;
		}
		break;

	case ULS_KEYW_TYPE_INTRINSIC:
		if (len_kwrd > ULS_2CHARS_TOKSIZ) {
			err_log("#%d: %s(%s) too long for 2-chars token", lno, wrd[0], wrd[1]);
			return NULL;
		}

		if (uls_decode_utf8((uls_char_t*) wrd[1], &uch) <= 1) {
			ch = wrd[1][0];
			ch_ctx[ch] |= ULS_CH_2PLUS;
		}
		break;

	case ULS_KEYW_TYPE_IDSTR:
		if (tok_id >= 0 && tok_id < ULS_SYNTAX_TABLE_SIZE) {
			if (ch_ctx[tok_id] & ULS_CH_1) {
				if (uls_loglevel_isset(uls, ULS_LOG_NOTICE))
					err_log("Invalidate char-token, %d, ...", tok_id);
				ch_ctx[tok_id] &= ~ULS_CH_1;
			}
		}
		break;

	default:
		err_log("#%d: '%s' Unknown keyword!", lno, wrd[1]);
		return NULL;
		break;
	}

	uls_strcpy(e->keyword, wrd[1]);
	e->l_keyword = len_kwrd;
	e->tok_id = tok_id;

	if (e->kwrd_type == ULS_KEYW_TYPE_INTRINSIC) {
		++hdr->n_keys_intrinsic;
	} else if (e->kwrd_type == ULS_KEYW_TYPE_IDSTR) {
		++hdr->n_keys_idstr;
	}

	if (e_grp != NULL) {
		append_to_tokid_group(e, e_grp);
	} else {
		e->flags |= ULS_TOKDEF_FL_TOKID_LEADER;
		++hdr->n_tokids_uniq;
	}

	return e;
}

static int comp_by_tokid(const void* a, const void* b)
{
	uls_tokdef_t *e1 = *((uls_tokdef_t **) a);
	uls_tokdef_t *e2 = *((uls_tokdef_t **) b);
	int stat;

	if (e1->tok_id > e2->tok_id) stat = 1;
	else if (e1->tok_id < e2->tok_id) stat = -1;
	else stat = 0;

	return stat;
}

static void
init_reserved_toks(uls_lex_t* uls)
{
	uls_tokdef_t *e;
	int i;

	for (i=0; i<N_RESEVED_TOKS; i++) {
		uls->lxm_array[i] = (uls_tokdef_t *) uls_malloc(
			sizeof(uls_tokdef_t));
	}
	uls->n_lxm_array = N_RESEVED_TOKS;

/* LINENUM */
	e = uls->lxm_array[LINENUM_TOK_IDX];
	uls_strcpy(e->name, "TOK_LINENUM");
	uls_strcpy(e->keyword, "<LINE>");
	e->tok_id = uls->toknum_LINENUM;

/* NONE */
	e = uls->lxm_array[NONE_TOK_IDX];
	uls_strcpy(e->name, "TOK_NONE");
	uls_strcpy(e->keyword, "<NONE>");
	e->tok_id = uls->toknum_NONE;

/* ERR */
	e = uls->lxm_array[ERR_TOK_IDX];
	uls_strcpy(e->name, "TOK_ERR");
	uls_strcpy(e->keyword, "<ERR>");
	e->tok_id = uls->toknum_ERR;

/* EOI */
	e = uls->lxm_array[EOI_TOK_IDX];
	uls_strcpy(e->name, "TOK_EOI");
	uls_strcpy(e->keyword, "<EOI>");
	e->tok_id = uls->toknum_EOI;

/* EOF */
	e = uls->lxm_array[EOF_TOK_IDX];
	uls_strcpy(e->name, "TOK_EOF");
	uls_strcpy(e->keyword, "<EOF>");
	e->tok_id = uls->toknum_EOF;

/* ID */
	e = uls->lxm_array[ID_TOK_IDX];
	uls_strcpy(e->name, "TOK_ID");
	uls_strcpy(e->keyword, "<ID>");
	e->tok_id = uls->toknum_ID;

/* NUMBER */
	e = uls->lxm_array[NUM_TOK_IDX];
	uls_strcpy(e->name, "TOK_NUMBER");
	uls_strcpy(e->keyword, "<NUMBER>");
	e->tok_id = uls->toknum_NUMBER;

	for (i=0; i<uls->n_lxm_array; i++) {
		e = uls->lxm_array[i];
		e->l_keyword = uls_strlen(e->keyword);
	}
}

static void
normalize_kwrd_stat_list(uls_kwrd_stat_t *lst, int n_lst)
{
	double sum = 0.;
	int i, n=0, avg;

	for (i=0; i<n_lst; i++) {
		if (lst[i].freq > 0) {
			sum += lst[i].freq;
			++n;
		}
	}

	if (n==0) {
		avg = 1;
	} else {
		avg = (int) (sum / n);
	}

	for (i=0; i<n_lst; i++) {
		if (lst[i].freq < 0) {
			lst[i].freq = avg;
		}
	}
}

static int
ulf_read_config_var(int lno, char* lptr, ulf_header_t *hdr)
{
	char  *wrd[1+ULF_MAXDIM_HASHSPACE], *ptr;
	int i, len, n_parms, stat = 0;
	uls_version_t ver;

	n_parms = uls_get_column_words(lptr, ' ', wrd, 1+ULF_MAXDIM_HASHSPACE);

	if (uls_streql(wrd[0], "INITIAL_HASHCODE:")) {
		for (i=1; i<n_parms; i++) {
			ptr = wrd[i];
			if (ptr[0] == '0' && ptr[1] == 'x') {
				ptr += 2;
				hdr->init_hcode[i] = skip_atox(&ptr);
			} else {
				hdr->init_hcode[i] = skip_atou(&ptr);
			}
		}

		if (i <= 1) {
			err_log("Not specified dim of hash-space!");
			stat = -1;
		} else {
			hdr->n_init_hcode = i - 1;
		}

	} else if (uls_streql(wrd[0], "FILE_VERSION:")) {
		if (uls_version_pars_str(wrd[1], &ver) < 0 ||
			uls_version_cmp_code(&ver, &hdr->filever) < 2) {
			err_log("unsupported version(ulf)");
			stat = -1;
		}

		uls_version_copy(&hdr->filever, &ver);

	} else if (uls_streql(wrd[0], "HASH_ALGORITHM:")) {
		len = uls_str_toupper(wrd[1], wrd[1], -1);

		if (len > ULS_LEXSTR_MAXSIZ || !uls_streql(wrd[1], ULS_HASH_ALGORITHM)) {
			stat = -1;
		} else {
			uls_strcpy(hdr->hash_algorithm, wrd[1]);
		}

	} else if (uls_streql(wrd[0], "HASH_TABLE_SIZE:")) {
		if ((len=atoi(wrd[1])) <= 0) {
			err_log("Improper hash_table_size", len);
			stat = -1;
		} else {
			hdr->hash_table_size = len;
		}

	} else if (uls_streql(wrd[0], "HASH_VERSION:")) {
		if (uls_version_pars_str(wrd[1], &ver) < 0 ||
			uls_version_cmp_code(&ver, &hdr->hfunc_ver) < 2) {
			err_log("unsupported version(hash-ulf)");
			stat = -1;
		}

		uls_version_copy(&hdr->hfunc_ver, &ver);

	} else {
		err_log("%s: unknown attribute in ULF", wrd[0]);
		stat = -1;
	}
	
	return stat;
}

static int
ulf_read_header(FILE* fin, ulf_header_t *hdr)
{
	char  linebuff[ULS_LINEBUFF_SIZ], *lptr;
	int   linelen, lno;

	/* Read Header upto '%%' */
	lno = 0;
	while (fgets(linebuff, ULS_LINEBUFF_SIZ, fin) != NULL) {
		++lno;

		if ((linelen=strlen(linebuff))>0 && linebuff[linelen-1]=='\n') {
			linebuff[--linelen] = '\0';
		}

		if (uls_strncmp(linebuff, "%%%%", 2)==0) {
			break;
		}

		lptr = skip_space_chars(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		if (ulf_read_config_var(lno, lptr, hdr) < 0) {
			return -1;
		}
	}

	return lno;
}

static int kwrd_stat_comp_by_kwrd(const void* a, const void* b)
{
	uls_kwrd_stat_t *a1 = (uls_kwrd_stat_t *) a;
	uls_kwrd_stat_t *b1 = (uls_kwrd_stat_t *) b;

	return strcmp((char*) a1->kwrd, (char*) b1->kwrd);
}

static void
ulf_init_header(ulf_header_t *hdr)
{
	uls_version_make(&hdr->filever, 0, 0, 0, 0);
	uls_version_make(&hdr->hfunc_ver, 0, 0, 0, 0);

	hdr->init_hcode[0] = 0;
	hdr->n_init_hcode = 1;

	uls_strcpy(hdr->hash_algorithm, ULS_HASH_ALGORITHM);
	hdr->hash_table_size = ULF_HASH_TABLE_SIZE;
}

static uls_kwrd_stat_t*
load_lex_ulf(uls_tokdef_t *tok_info_lst, int n_tok_info_lst, int *p_n_list,
	FILE *fin, ulf_header_t *hdr, uls_kwtable_t* uls_kwtable)
{
	uls_kwrd_stat_t *lst, *kwrd_stat;
	uls_tokdef_t *e, *e_next;
	int   n_lst;
	char  linebuff[ULS_LINEBUFF_SIZ], *lptr, *wrd[2];
	int   linelen, lno, i;

	// set the default values for ulf
	ulf_init_header(hdr);

	uls_version_make(&hdr->filever,
		ULF_VERSION_MAJOR, ULF_VERSION_MINOR,
		ULF_VERSION_DEBUG, ULF_VERSION_SEQUENCE);

	uls_version_make(&hdr->hfunc_ver,
		ULF_VERSION_HASHFUNC_MAJOR, ULF_VERSION_HASHFUNC_MINOR,
		ULF_VERSION_HASHFUNC_DEBUG, ULF_VERSION_HASHFUNC_SEQUENCE);

	for (e=tok_info_lst, n_lst=0; e!=NULL; e=e->hlink, n_lst++) {
		/* NOTHING */;
	}
	if (n_tok_info_lst < n_lst) n_lst = n_tok_info_lst;

	lst = (uls_kwrd_stat_t *) uls_malloc(n_lst * sizeof(uls_kwrd_stat_t));

	// consume tok_info_lst upto n_lst
	for (i=0, e=tok_info_lst; e!=NULL; e=e_next) {
		e_next = e->hlink;
		e->hlink = NULL;

		lst[i].kwrd = e->keyword;
		lst[i].freq = -1;
		lst[i].kwrd_info = e;
		++i;
	}

	qsort(lst, n_lst, sizeof(uls_kwrd_stat_t), kwrd_stat_comp_by_kwrd);

	if ((lno=ulf_read_header(fin, hdr)) < 0) {
		err_log("%s: fail to read ulf-header", __FUNCTION__);
		uls_free_kwrd_stat_list(lst);
		return NULL;
	}

	uls_reset_kwtable(uls_kwtable, hdr->hash_table_size, hdr->n_init_hcode, hdr->init_hcode);

	while (fgets(linebuff, ULS_LINEBUFF_SIZ, fin) != NULL) {
		++lno;

		if ((linelen=strlen(linebuff))>0 && linebuff[linelen-1]=='\n') {
			linebuff[--linelen] = '\0';
		}

		lptr = skip_space_chars(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		uls_get_column_words(lptr, ' ', wrd, 2);

		if ((kwrd_stat=uls_search_kwrd_stat_sorted(lst, n_lst, (uls_char_t *) wrd[0])) != NULL) {
			kwrd_stat->freq = atoi(wrd[1]);
		}
	}

	normalize_kwrd_stat_list(lst, n_lst);
	*p_n_list = n_lst;

	return lst;
}

static int kwrd_stat_comp_by_freq(const void* a, const void* b)
{
	uls_kwrd_stat_t *a1 = (uls_kwrd_stat_t *) a;
	uls_kwrd_stat_t *b1 = (uls_kwrd_stat_t *) b;

	return a1->freq - b1->freq;
}

void
free_lxm_array(uls_lex_t* uls)
{
	int i;

	for (i=0; i<uls->n_lxm_array; i++) {
		uls_mfree(uls->lxm_array[i]);
	}

	uls->siz_lxm_array = uls->n_lxm_array = 0;
	uls->lxm_array = uls_mfree(uls->lxm_array);
}

int
load_lex_conf(FILE *fin, FILE *fin_ulf, uls_lex_t* uls)
{
	ulc_header_t  uls_config;
	char     linebuff[ULS_LINEBUFF_SIZ], *lptr;
	char     *str_pool;
	int      linelen, lno;
	int      i, stat = 0;
	int      n, n1, n2;

	uls_tokdef_t *e, *e_next, *tok_info_list;
	uls_kwrd_stat_t *kwrd_stat_list;
	int n_kwrd_stat_list = 0;
	ulf_header_t ulf_hdr;

	uls_config.n_keys_intrinsic = uls_config.n_keys_idstr = 0;
	uls_config.n_tokids_uniq = N_RESEVED_TOKS;
	uls_config.tok_id_seed = 0;
	uls_config.tok_id_min = uls_config.tok_id_max = 0;
	uls_version_make(&uls_config.filever, 0, 0, 0, 0);

	uls->siz_lxm_array = N_RESEVED_TOKS + LXM_LINES_DELTA;
	uls->lxm_array = (uls_tokdef_t **)
		uls_malloc(uls->siz_lxm_array * sizeof(uls_tokdef_t *));
	uls->n_lxm_array = 0;

	init_reserved_toks(uls);

	if ((lno=ulc_read_header(uls, fin, &uls_config)) < 0) {
		err_log("fail to read the header of uls-spec.");
		return -1;
	}

	if (classify_char_group(uls, &uls_config) < 0) {
		err_log("%s: lex-conf file not consistent!", __FUNCTION__);
		return -1;
	}

	while ((fgets(linebuff, ULS_LINEBUFF_SIZ, fin))!=NULL) {
		++lno;

		if ((linelen=strlen(linebuff))>0 && linebuff[linelen-1]=='\n') {
			linebuff[--linelen] = '\0';
		}

		lptr = skip_space_chars(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		if ((e=conf_proc_line(lno, uls, lptr, &uls_config)) == NULL) {
			stat = -1; break;
		}

		if (e->tok_id < uls_config.tok_id_min)
			uls_config.tok_id_min = e->tok_id;

		if (e->tok_id > uls_config.tok_id_max)
			uls_config.tok_id_max = e->tok_id;
	}

	if (stat < 0 || check_rsvd_toks(uls) < 0) {
		free_lxm_array(uls);
		return -1;
	}

	uls->toknum_LINENUM = uls->lxm_array[LINENUM_TOK_IDX]->tok_id;
	uls->toknum_NONE = uls->lxm_array[NONE_TOK_IDX]->tok_id;
	uls->toknum_ERR = uls->lxm_array[ERR_TOK_IDX]->tok_id;
	uls->toknum_EOI = uls->lxm_array[EOI_TOK_IDX]->tok_id;
	uls->toknum_EOF = uls->lxm_array[EOF_TOK_IDX]->tok_id;
	uls->toknum_ID = uls->lxm_array[ID_TOK_IDX]->tok_id;
	uls->toknum_NUMBER = uls->lxm_array[NUM_TOK_IDX]->tok_id;

	// shrink the size of uls->lxm_array to the compact size.
	uls->siz_lxm_array = uls->n_lxm_array;
	uls->lxm_array = (uls_tokdef_t **) uls_mrealloc(uls->lxm_array,
		uls->siz_lxm_array * sizeof(uls_tokdef_t *));

	// malloc for lxm_tokid_sorted[] and lxm_intrinsic[]
	str_pool = uls_malloc(
		(uls_config.n_tokids_uniq + uls_config.n_keys_intrinsic) * sizeof(uls_tokdef_t *));

	uls->lxm_tokid_sorted = (uls_tokdef_t **) str_pool;
	str_pool += uls_config.n_tokids_uniq * sizeof(uls_tokdef_t *);

	if (uls_config.n_keys_intrinsic > 0) {
		uls->lxm_intrinsic = (uls_tokdef_t **) str_pool;
		str_pool += uls_config.n_keys_intrinsic * sizeof(uls_tokdef_t *);
	} else {
		uls->lxm_intrinsic = NULL;
	}

	n = n1 = n2 = 0;
	tok_info_list = NULL;

	for (i=0; i<N_RESEVED_TOKS; i++) {
		e = uls->lxm_array[i];
		uls->lxm_tokid_sorted[n++] = e;
	}

	for ( ; i < uls->n_lxm_array; i++) {
		e = uls->lxm_array[i];

		/* lxm_intrinsic or lxm_keyw_table */
		if (e->kwrd_type == ULS_KEYW_TYPE_INTRINSIC) {
			uls->lxm_intrinsic[n1++] = e;
		} else if (e->kwrd_type == ULS_KEYW_TYPE_IDSTR) {
			e->hlink = tok_info_list;
			tok_info_list = e;
			++n2;
		} else if (e->kwrd_type == ULS_KEYW_TYPE_1CHAR) {
			e->hlink = uls->lxm_1char;
			uls->lxm_1char = e;
		}

		if (e->flags & ULS_TOKDEF_FL_TOKID_LEADER) {
			uls->lxm_tokid_sorted[n++] = e;
		}
	}

	uls->n_lxm_tokid_sorted = n;
	uls->n_lxm_intrinsic = n1;

	qsort(uls->i_str.commtypes, uls->i_str.n_commtypes, sizeof(istr_commtype_t), commtype_by_length);
	qsort(uls->i_str.quotetypes, uls->i_str.n_quotetypes, sizeof(istr_quotetype_t), quotetype_by_length);

	qsort(uls->lxm_tokid_sorted, uls->n_lxm_tokid_sorted, sizeof(uls_tokdef_t *), comp_by_tokid);
	qsort(uls->lxm_intrinsic, uls->n_lxm_intrinsic, sizeof(uls_tokdef_t *), comp_intrinsic_id_by_length);

	uls_init_kwtable(&uls->lxm_keyw_table, 0, uls->lxm_strcmp);

	if (fin_ulf != NULL && (kwrd_stat_list=load_lex_ulf(tok_info_list, n2, &n_kwrd_stat_list,
		fin_ulf, &ulf_hdr, &uls->lxm_keyw_table)) != NULL) {
		memcpy(uls->lxm_keyw_table.init_hcode, ulf_hdr.init_hcode,
			ulf_hdr.n_init_hcode * sizeof(uls_uint32));
		uls->lxm_keyw_table.n_init_hcode = ulf_hdr.n_init_hcode;

		qsort(kwrd_stat_list, n_kwrd_stat_list, sizeof(uls_kwrd_stat_t), kwrd_stat_comp_by_freq);

		for (i=0; i < n_kwrd_stat_list; i++) {
			e = kwrd_stat_list[i].kwrd_info;
			uls_add_kw(&uls->lxm_keyw_table, e);
		}

		uls_free_kwrd_stat_list(kwrd_stat_list);

	} else {
		uls_init_kwtable(&uls->lxm_keyw_table, ULF_HASH_TABLE_SIZE, uls->lxm_strcmp);
		ulf_init_header(&ulf_hdr);

		// consume tok_info_lst upto n_lst
		for (e=tok_info_list; e!=NULL; e=e_next) {
			e_next = e->hlink;
			uls_add_kw(&uls->lxm_keyw_table, e);
		}
	}

	return stat;
}

int
uls_is_valid_specname(const char* confname)
{
	uls_char_t ch;
	int i;

	for (i=0; (ch=confname[i])!='\0'; i++) {
		if (!(isalnum(ch) || ch=='.' || ch=='-' || ch=='_')) {
			return -1;
		}
	}

	if (i > ULS_LEXSTR_MAXSIZ) {
		return -2;
	}

	return i;
}

int
uls_get_specname(const char *fpath, char* fnameAZ)
{
	const char *fname, *end_ptr;
	int len;

	fname = uls_filename(fpath);

	if ((end_ptr = uls_strchr_r(fname, '.'))==NULL)
		len = strlen(fname);
	else
		len = (int) (end_ptr - fname);

	if (len > ULS_LEXSTR_MAXSIZ) {
		err_log("%s: has too long spec-name", fpath);
		return -1;
	}

	strncpy(fnameAZ, fname, len);
	fnameAZ[len] = '\0';
	if (uls_is_valid_specname(fnameAZ) < 0) {
		err_log("%s: improper for spec-name format", fname);
		return -1;
	}

	return len;
}

static uls_tokdef_t*
get_tokdefs_from_named_quotetypes(uls_lex_t* uls, int* n_tokdefs)
{
	uls_tokdef_t   *tokdefs, *e;
	istr_quotetype_t *q;
	int i, n;

	tokdefs = (uls_tokdef_t *) uls_malloc(uls->i_str.n_quotetypes * sizeof(uls_tokdef_t));

	for (n=i=0; i<uls->i_str.n_quotetypes; i++) {
		q = uls->i_str.quotetypes + i;

		if (q->name[0] != '\0') {
			e = tokdefs + n;

			e->tok_id = q->tok_id;
			uls_strcpy(e->name, q->name);
			uls_strcpy(e->keyword, "?");
			e->l_keyword = 1;
			e->kwrd_type = ULS_KEYW_TYPE_LITERAL;
			e->hlink = NULL;

			++n;
		}
	}

	*n_tokdefs = n;
	if (n == 0) {
		uls_mfree(tokdefs);
		return NULL;
	}

	return tokdefs;
}

static int
filter_to_print_tokdef(uls_lex_t* uls, uls_tokdef_t *e, int flags)
{
	int stat = 0;

	if (e->tok_id == uls->toknum_LINENUM ||
		e->tok_id == uls->toknum_NONE) {
		stat = 1; // filtered

	} else if (is_resevered_tok(uls, e->name) >= 0) {
		if ((flags & ULS_FL_WANT_RESERVED_TOKS) == 0) stat = 1;

	} else if (find_quotetype_by_tokid(
		uls->i_str.quotetypes, uls->i_str.n_quotetypes, e->tok_id) != NULL) {
		if ((flags & ULS_FL_WANT_QUOTE_TOKS) == 0) stat = 1;

	} else {
		if ((flags & ULS_FL_WANT_REGULAR_TOKS) == 0) stat = 1;
	}

	if (stat) {
		if (uls_loglevel_isset(uls, ULS_LOG_NOTICE))
			err_log("'%s', filtered when printing the header file.", e->name);
	}
	return stat;
}

static void
print_tabs(FILE *fout, int n)
{
	uls_char_t buf[16];
	int i, buf_len;

	buf_len = sizeof(buf) - 1;
	memset(buf, '\t', buf_len);
	buf[buf_len] = '\0';

	for (i=0; i<n/buf_len; i++)
		uls_fprintf(fout, "%s", buf);

	if ((i=n % buf_len) > 0) {
		buf[i] = '\0';
		uls_fprintf(fout, "%s", buf);
	}
}

static void
tab_fprintf(FILE* fp, int n_tabs, uls_lf_char_t *fmt, ...)
{
	va_list args;
	int len;

	print_tabs(fp, n_tabs);

	va_start(args, fmt);
	len = uls_vfprintf(fp, fmt, args);
	va_end(args);
}

static void
print_tokdef_c_header(uls_lex_t* uls, FILE *fout,
	uls_tokdef_t *lxm_ary_prn[], int n_lxm_ary_prn, char* enum_name, int flags)
{
	int      i, prev_tok_id, sect_lead;
	uls_tokdef_t *e;

	if (n_lxm_ary_prn <= 0) return;

	if (flags & ULS_FL_C_ENUM)  {
		if (n_lxm_ary_prn > 0) prev_tok_id = lxm_ary_prn[0]->tok_id;
		else prev_tok_id = -1; // N/A

		uls_fprintf(fout, "enum %s {\n", enum_name);

		for (i=0; i<n_lxm_ary_prn; ) {
			e = lxm_ary_prn[i];

			sect_lead = (e->tok_id % 10 == 0);
			if (sect_lead || prev_tok_id + 1 != e->tok_id) {
				uls_fprintf(fout, "\t%16s = %d", e->name, e->tok_id);
			} else {
				uls_fprintf(fout, "\t%16s", e->name);
			}

			prev_tok_id = e->tok_id;

			if (++i == n_lxm_ary_prn) {
				uls_fprintf(fout, "\n");
				break;
			}
			uls_fprintf(fout, ",\n");
		}

		uls_fprintf(fout, "};\n");

	} else {
		for (i=0; i<n_lxm_ary_prn; i++) {
			e = lxm_ary_prn[i];

			if (e->tok_id % 10 == 0 || i==N_RESEVED_TOKS) uls_fprintf(fout, "\n");
			uls_fprintf(fout, "#define %16s  %-3d\n", e->name, e->tok_id);
		}
	}

}

static void
print_tokdef_cpp(uls_lex_t* uls, FILE *fout,
	uls_tokdef_t *lxm_ary_prn[], int n_lxm_ary_prn, char* class_name, int flags)
{
	uls_tokdef_t *e;
	int i, last_enum, prev_tok_id, n_wrd, n_tabs;
	char namebuff[1024];
	char *wrd[32];

	if (n_lxm_ary_prn <= 0) return;

	uls_strcpy(namebuff, class_name);
	n_wrd = uls_get_column_words(namebuff, '.', wrd, 32);
	class_name = wrd[--n_wrd];

	uls_fprintf(fout, "#include \"UlsLex.h\"\n\n");

	for (n_tabs=0; n_tabs<n_wrd; n_tabs++) {
		tab_fprintf(fout, n_tabs, "namespace %s {\n", wrd[n_tabs]);
	}

	tab_fprintf(fout, n_tabs, "class %s : public ULS::UlsLex {\n", class_name);
 	tab_fprintf(fout, n_tabs, "  public:\n");

	if (n_lxm_ary_prn > 0) prev_tok_id = lxm_ary_prn[0]->tok_id;
	else prev_tok_id = -1; // N/A

	++n_tabs;
	tab_fprintf(fout, n_tabs, "enum TokenType {\n");

	for (i=0; i<n_lxm_ary_prn; i++) {
		e = lxm_ary_prn[i];

		last_enum = (i+1 == n_lxm_ary_prn) ? 1 : 0;

		if (prev_tok_id + 1 != e->tok_id) {
			if (last_enum) 
				tab_fprintf(fout, n_tabs, "%12s = %d\n",  e->name, e->tok_id);
			else
				tab_fprintf(fout, n_tabs, "%12s = %d,\n",  e->name, e->tok_id);
		} else {
			if (last_enum) 
				tab_fprintf(fout, n_tabs, "%12s\n", e->name);
			else
				tab_fprintf(fout, n_tabs, "%12s,\n", e->name);
		}

		prev_tok_id = e->tok_id;
	}

	tab_fprintf(fout, n_tabs, "};\n\n");

	tab_fprintf(fout, n_tabs, "%s(std::string& ulc_file) : UlsLex(ulc_file) {}\n", class_name);
	tab_fprintf(fout, n_tabs, "virtual ~%s() {}\n", class_name);

	--n_tabs;
	tab_fprintf(fout, n_tabs, "};\n");

	for (--n_tabs; n_tabs >= 0; n_tabs--) {
		tab_fprintf(fout, n_tabs, "}\n");
	}
}

int
uls_generate_tokdef_file(uls_lex_t* uls, char *fpath, char* ulc_name, char* class_name, int flags)
{
	char     pathbuff[ULS_FILEPATH_MAX+1];
	char     fname[ULS_LEXSTR_MAXSIZ+1];
	char     fnameAZ[ULS_LEXSTR_MAXSIZ+1];
	char     *ptr, *end_ptr;
	int      i, len, n_qmt_tokdefs;
	uls_tokdef_t *e, *qmt_tokdefs;

	int   n_lxm_ary_prn, n_alloc_lxm_ary_prn;
	uls_tokdef_t **lxm_ary_prn;
	FILE    *fout;

	if (class_name == NULL)
		class_name = ulc_name;

	if (fpath == NULL) {
		uls_strcpy(pathbuff, ulc_name);
		i = strlen(pathbuff);
		uls_strcpy(pathbuff+i, "_lex.h");
		fpath = pathbuff;
	}

	if (uls->lxm_array==NULL || uls->n_lxm_array==0) {
		err_log("%s: tok-config file NOT YET loaded", __FUNCTION__);
		return -1;
	}

	ptr = uls_filename(fpath);
	if ((end_ptr = strrchr(ptr, '.'))==NULL) len = strlen(ptr);
	else len = (int) (end_ptr - ptr);

	if (len > ULS_LEXSTR_MAXSIZ) {
		err_log("%s: too long filename", fpath);
		return -1;
	}

	strncpy(fname, ptr, len);
	fname[len] = '\0';

	if ((fout=fopen(fpath, "w")) == NULL) {
		err_log("%s: fail to create '%s'", __FUNCTION__, fpath);
		return -1;
	}

	if (!(flags & ULS_FL_SILENT))
		err_log("Writing the lexical information of '%s' to %s, ...", class_name, fpath);

	qmt_tokdefs = get_tokdefs_from_named_quotetypes(uls, &n_qmt_tokdefs);

	n_alloc_lxm_ary_prn = n_qmt_tokdefs + uls->n_lxm_array;
	lxm_ary_prn = (uls_tokdef_t **) uls_malloc(n_alloc_lxm_ary_prn * sizeof(uls_tokdef_t *));
	n_lxm_ary_prn = 0;

	for (i=0; i < n_qmt_tokdefs; i++) {
		e = qmt_tokdefs + i;
		if (filter_to_print_tokdef(uls, e, flags) == 0) {
			lxm_ary_prn[n_lxm_ary_prn++] = e;
		}
	}

	for (i=0; i < uls->n_lxm_array; i++) {
		e = uls->lxm_array[i];

		if (__find_lxm(uls, e->name, NULL, N_RESEVED_TOKS, i) == NULL &&
			filter_to_print_tokdef(uls, e, flags) == 0) {
			lxm_ary_prn[n_lxm_ary_prn++] = e;
		}
	}

	qsort(lxm_ary_prn, n_lxm_ary_prn, sizeof(uls_tokdef_t *), comp_by_tokid);

	uls_fprintf(fout, "//\n");
	uls_fprintf(fout, "// THIS FILE WAS AUTOMATICALLY GENERATED.\n");
	uls_fprintf(fout, "// DO NOT EDIT!\n");
	uls_fprintf(fout, "//\n\n");

	uls_str_toupper(fname, fnameAZ, -1);
	uls_fprintf(fout, "#ifndef __%s_H__\n", fnameAZ);
	uls_fprintf(fout, "#define __%s_H__\n\n", fnameAZ);

	if (flags & ULS_FL_C_CONSTANTS_GEN) {
		print_tokdef_c_header(uls, fout, lxm_ary_prn, n_lxm_ary_prn, class_name, flags);
	} else {
		// C++ class generated!
		print_tokdef_cpp(uls, fout, lxm_ary_prn, n_lxm_ary_prn, class_name, flags);
	}

	uls_fprintf(fout, "\n#endif\n\n");

	uls_mfree(lxm_ary_prn);
	uls_mfree(qmt_tokdefs);

	fclose(fout);

	return 0;
}

int
ulf_create_file(int n_hcodes, uls_uint32 *hcodes, int htab_siz, uls_kwrd_stat_t *lst, int n_lst, FILE* fout)
{
	int i;

	if (htab_siz <= 0) return -1;

	uls_fprintf(fout, "\n");
	uls_fprintf(fout, "FILE_VERSION: %u.%u.%u.%u\n",
		ULF_VERSION_MAJOR, ULF_VERSION_MINOR, ULF_VERSION_DEBUG, ULF_VERSION_SEQUENCE);

	uls_fprintf(fout, "HASH_ALGORITHM: %s\n", ULS_HASH_ALGORITHM);
	uls_fprintf(fout, "HASH_VERSION: %u.%u.%u.%u\n",
		ULF_VERSION_HASHFUNC_MAJOR, ULF_VERSION_HASHFUNC_MINOR,
		ULF_VERSION_HASHFUNC_DEBUG, ULF_VERSION_HASHFUNC_SEQUENCE);

	uls_fprintf(fout, "HASH_TABLE_SIZE: %d\n", htab_siz);

	uls_fprintf(fout, "INITIAL_HASHCODE: ");
	for (i=0; i<n_hcodes; i++) {
		uls_fprintf(fout, " 0x%08X", hcodes[i]);
	}
	uls_fprintf(fout, "\n");

	uls_fprintf(fout, "\n%%%%\n\n");

	for (i=0; i<n_lst; i++) {
		if (lst[i].freq >= 0)
			uls_fprintf(fout, "%-24s %d\n", lst[i].kwrd, lst[i].freq);
	}

	return 0;
}

uls_tokdef_t*
is_keyword_intrinsic(uls_lex_t* uls, uls_char_t* str, int len)
{
	uls_tokdef_t *e, *e_ret=NULL;
	int i;

	if (len < 0) len = uls_strlen(str);

	for (i=0; i < uls->n_lxm_intrinsic; i++) {
		e = uls->lxm_intrinsic[i];
		if (len >= e->l_keyword && uls_strncmp(str, e->keyword, e->l_keyword)==0) {
			e_ret = e;
			break;
		}
	}

	return e_ret;
}

uls_tokdef_t*
is_keyword_idstr(uls_lex_t* uls, uls_char_t* kwrd, int l_kwrd)
{
	return uls_find_kw(&uls->lxm_keyw_table, kwrd, l_kwrd, NULL);
}

uls_tokdef_t*
is_keyword_1char(uls_lex_t* uls, uls_char_t kwrd)
{
	uls_tokdef_t *e;

	for (e=uls->lxm_1char; e != NULL; e=e->hlink) {
		if (e->keyword[0] == kwrd) {
			return e;
		}
	}
	
	return e;
}

static int
srch_by_id(void* a, uls_char_t* p_id)
{
	uls_tokdef_t *e = (uls_tokdef_t *) a;
	int id, stat;

	id = *((int *) p_id);

	/*  (-1) mid(0) (+1) */
	if (e->tok_id < id) stat = -1;
	else if (e->tok_id > id) stat = 1;
	else stat = 0;

	return stat;
}

uls_char_t*
get_keyword_str(uls_lex_t* uls, int t)
{
	uls_tokdef_t *e;
	uls_char_t *str;

	e = (uls_tokdef_t *) uls_bi_search((uls_char_t *) &t,
		(void *) uls->lxm_tokid_sorted, uls->n_lxm_tokid_sorted,
		srch_by_id);

	if (e == NULL) str = NULL;
	else str = e->keyword;

	return str;
}

int canbe_commtype_mark(uls_char_t* wrd, uls_char_t *str, int* p_len, int* p_n_lfs)
{
	int i, n=0;
	uls_char_t ch;

	*p_len = uls_get_escape_str('\0', (char *) wrd, (char *) str);

	for (i=0; (ch=str[i])!='\0'; i++) {
		if (ch == '\n') {
			++n;
		} else if (!isgraph(ch)) {
			return 0;
		}
	}

	*p_n_lfs = n;

	return 1;
}

int canbe_quotetype_mark(uls_char_t* wrd, uls_char_t *str, int* p_len)
{
	int i;
	uls_char_t ch;

	*p_len = uls_get_escape_str('\0', (char *) wrd, (char *) str);

	for (i=0; (ch=str[i])!='\0'; i++) {
		if (!isgraph(ch)) {
			return 0;
		}
	}

	return 1;
}

int
ulf_hashfunc(const uls_char_t *name, int n_slots, int n_shifts,
	int n_hcodes, uls_uint32 *hcodes)
{
	uls_uint32 a, codes[ULF_MAXDIM_HASHSPACE];
	int i, j, k;

	if (n_slots <= 0 ||  n_hcodes <= 0) return 0;

	if (n_shifts < 0) {
		n_shifts = uls_range_of_bits(n_slots) - 8;
		if (n_shifts < 0) n_shifts = 0;
	}

	for (i=0; i<n_hcodes; i++) codes[i] = hcodes[i];

	for (i=0; (a=name[i])!='\0'; i++) {
		j = i % (n_shifts + 1);
		k = i % n_hcodes;
		codes[k] = (codes[k] << 1) ^ (a << j);
	}

	a = codes[0];
	for (i=1; i<n_hcodes; i++) a ^= codes[i];

	return a % n_slots;
}

static void
__reset_kwtable_buckets(uls_kwtable_t* tbl, int n_slots)
{
	if (n_slots > 0) {
		tbl->bucket_head = (uls_tokdef_t **)
			uls_malloc(n_slots * sizeof(uls_tokdef_t *));
		memset(tbl->bucket_head, 0x00, n_slots * sizeof(uls_tokdef_t *));
		tbl->n_buckets = n_slots;
		tbl->n_shifts = uls_range_of_bits(n_slots) - 8;
		if (tbl->n_shifts < 0) tbl->n_shifts = 0;
	} else {
		tbl->bucket_head = NULL;
		tbl->n_buckets = 0;
		tbl->n_shifts = -1;
	}
}

void
uls_init_kwtable(uls_kwtable_t* tbl, int n_slots, uls_strcmp_t cmpfunc)
{
	__reset_kwtable_buckets(tbl, n_slots);

	tbl->lxm_strcmp = cmpfunc;
	tbl->init_hcode[0] = 0;
	tbl->n_init_hcode = 1;
}

void
uls_reset_kwtable(uls_kwtable_t* tbl, int n_slots, int n_hcodes, uls_uint32 *hcodes)
{
	tbl->bucket_head = uls_mfree(tbl->bucket_head);
	tbl->n_buckets = 0;

	__reset_kwtable_buckets(tbl, n_slots);

	memcpy(tbl->init_hcode, hcodes, n_hcodes*sizeof(uls_uint32));
	tbl->n_init_hcode = n_hcodes;
}

void
uls_deinit_kwtable(uls_kwtable_t* tbl)
{
	tbl->bucket_head = uls_mfree(tbl->bucket_head);
	tbl->n_buckets = 0;
}

uls_tokdef_t*
uls_find_kw(uls_kwtable_t* tbl, uls_char_t *idstr, int l_idstr, int* p_hash_val)
{
	uls_tokdef_t  *e, *e_found=NULL;
	int hash_id;

	hash_id = ulf_hashfunc(idstr, tbl->n_buckets, tbl->n_shifts, 
		tbl->n_init_hcode, tbl->init_hcode);
	if (p_hash_val != NULL)
		*p_hash_val = hash_id;

	for (e=tbl->bucket_head[hash_id]; e!=NULL; e=e->hlink) {
		if (tbl->lxm_strcmp(e->keyword, e->l_keyword, idstr, l_idstr) == 0) {
			e_found = e;
			break;
		}
	}

	return e_found; 
}

int
uls_add_kw(uls_kwtable_t* tbl, uls_tokdef_t* e0)
{
	int hash_id;

	if (uls_find_kw(tbl, e0->keyword, e0->l_keyword, &hash_id) == NULL) {
		e0->hlink = tbl->bucket_head[hash_id];
		tbl->bucket_head[hash_id] = e0;
		return 1;
	}

	return 0;
}

int
sizeof_kwtable(uls_lex_t* uls)
{
	uls_kwtable_t* tbl = &uls->lxm_keyw_table;
	uls_tokdef_t  *e;
	int i, n=0;

	for (i=0; i<tbl->n_buckets; i++) {
		if (tbl->bucket_head[i] == NULL) continue;
		for (e=tbl->bucket_head[i]; e!=NULL; e=e->hlink) {
			++n;
		}
	}

	return n;
}

static int
__export_kwtable(uls_lex_t* uls, uls_kwrd_stat_t *lst, int n_lst)
{
	uls_kwtable_t* tbl = &uls->lxm_keyw_table;
	uls_tokdef_t  *e;
	int i, k=0;

	if (n_lst == 0) return 0;

	for (i=0; i<tbl->n_buckets; i++) {
		if (tbl->bucket_head[i] == NULL) continue;
		for (e=tbl->bucket_head[i]; e!=NULL; e=e->hlink) {
			if (k>=n_lst) return -n_lst;
			lst[k].kwrd = e->keyword;
			lst[k].freq = -1;
			lst[k].kwrd_info = e;
			++k;
		}
	}

	qsort(lst, k, sizeof(uls_kwrd_stat_t), kwrd_stat_comp_by_kwrd);

	return k;
}

uls_kwrd_stat_t*
uls_export_kwtable(uls_lex_t* uls, int* p_n_list)
{
	uls_kwrd_stat_t *lst;
	int   n_lst, i;

	n_lst = sizeof_kwtable(uls);
	lst = (uls_kwrd_stat_t *) uls_malloc(
			n_lst * sizeof(uls_kwrd_stat_t));

	__export_kwtable(uls, lst, n_lst);
	// lst is sorted by lst[].kwrd

	for (i=0; i<n_lst; i++) {
		lst[i].freq = 0;
	}

	*p_n_list = n_lst;

	return lst;
}

void
uls_free_kwrd_stat_list(uls_kwrd_stat_t* lst)
{
	uls_mfree(lst);
}

uls_kwrd_stat_t*
uls_search_kwrd_stat_sorted(uls_kwrd_stat_t *lst, int n_lst, const uls_char_t* str)
{
	int   low, high, mid, cond;
	uls_kwrd_stat_t *e;

	low = 0;
	high = n_lst - 1;

	while (low <= high) {
		mid = (low + high) / 2;
		e = lst + mid;

		if ((cond = strcmp((char*) e->kwrd, (char*) str)) < 0) {
			low = mid + 1;
		} else if (cond > 0) {
			high = mid - 1;
		} else {
			return e;
		}
	}

	return NULL;
}

void
uls_version_make(uls_version_t* a,
	uls_byte_t v1, uls_byte_t v2, uls_byte_t v3, uls_byte_t v4)
{
	a->major = v1;
	a->minor = v2;
	a->debug = v3;
	a->sequence= v4;
}

void
uls_version_encode(const uls_version_t* a, uls_byte_t* codstr)
{
	codstr[0] = a->major;
	codstr[1] = a->minor;
	codstr[2] = a->debug;
	codstr[3] = a->sequence;
}

void
uls_version_decode(uls_byte_t* codstr, uls_version_t* a)
{
	a->major = codstr[0];
	a->minor = codstr[1];
	a->debug = codstr[2];
	a->sequence = codstr[3];
}

void
uls_version_copy(uls_version_t* dst, const uls_version_t* src)
{
	dst->major = src->major;
	dst->minor = src->minor;
	dst->debug = src->debug;
	dst->sequence = src->sequence;
}

int
uls_version_make_string(const uls_version_t* a, char* ver_str)
{
	char *ptr = ver_str;
	uls_byte_t codstr[ULS_VERSION_CODE_LEN];
	int i, n=0, l;

	uls_version_encode(a, codstr);

	for (i=0; i<ULS_VERSION_CODE_LEN; i++) {
		if (codstr[i] != 0) n = i + 1;
	}

	if (n==0) {
		*ptr++ = '0';
		*ptr = '\0';
	} else {
		ptr = ver_str;
		for (i=0; i<n; i++) {
			if (i!=0) *ptr++ = '.';
			l = uls_sprintf(ptr, "%u", codstr[i]);
			ptr += l;
		}
	}

	return (int) (ptr - ver_str);
}

int
uls_version_cmp_code(const uls_version_t* a, const uls_version_t* b)
{
	uls_byte_t codstr1[ULS_VERSION_CODE_LEN];
	uls_byte_t codstr2[ULS_VERSION_CODE_LEN];
	int i;

	uls_version_encode(a, codstr1);
	uls_version_encode(b, codstr2);

	for (i=0; i<ULS_VERSION_CODE_LEN; i++) {
		if (codstr1[i] != codstr2[i]) break;
	}

	return i;
}

int
uls_version_pars_str(char *lptr, uls_version_t* a)
{
	uls_byte_t codstr[ULS_VERSION_CODE_LEN];
	int i, n;

	for (i=0; i<ULS_VERSION_CODE_LEN; ) {
		if (!isdigit(*lptr)) break;

		codstr[i++] = (uls_char_t) skip_atou(&lptr);
		if (*lptr != '.' || !isdigit(*++lptr))
			break;
	}

	for (n=i; i<ULS_VERSION_CODE_LEN; i++)
		codstr[i] = 0;

	uls_version_decode(codstr, a);

	return n;
}

// <brief>
// Lists the directories in which ULS-object in order searches for ulc-file(*.ulc)
// that would be specified in uls_create('confname', ...).
// </brief>
// <parm name="confname">The name of lexcial configuration, as like 'sample' or 'sample.ulc'</parm>
// <return>none</return>
void
uls_list_ulc_searchpaths(const char* confname)
{
	const char   *title[8], *searchpath[8];
	char  fname_buff[ULS_LEXSTR_MAXSIZ+5];
	const char   *lptr, *lptr0, *fptr, *fname;
	char   fpath_buff[ULS_FILEPATH_MAX+1];
	int    i, rc, len, len_fptr, n = 0;

	err_log("Searching path for %s, ...", confname);

	len = strlen(confname);
	if ((lptr=uls_strchr_r(confname, '.'))!=NULL && uls_streql(lptr+1, "ulc")) {
		fname = confname;
		title[n] = "PWD";
		searchpath[n] = NULL;
		++n;
	} else {
		uls_strcpy(fname_buff, confname);
		uls_strcpy(fname_buff+len, ".ulc");
		fname = fname_buff;
	}

	if ((lptr=getenv("WHERE_ULC_ARE")) != NULL) {
		title[n] = "WHERE_ULC_ARE";
		searchpath[n] = lptr;
		++n;
	}

	title[n] = "ULS_CONFIG_DIR";
	searchpath[n] = ULS_CONFIG_DIR;
	++n;

	title[n] = "ULS_CONFIG_DFLDIR";
	searchpath[n] = ULS_CONFIG_DFLDIR;
	++n;

	for (i=0; i<n; i++) {
		if (searchpath[i] == NULL) {
			searchpath[i] = uls_getcwd(fpath_buff, sizeof(fpath_buff));
			if (searchpath[i] == NULL) continue;
		}

		err_log("[%s]", title[i]);
		for (lptr0=searchpath[i]; lptr0 != NULL; ) {
			if ((lptr = strchr(lptr0, ULS_DELIMCH_DIRPATH)) != NULL) {
				len_fptr = (int) (lptr - lptr0);
				fptr = lptr0;
				lptr0 = ++lptr;
			} else {
				len_fptr = strlen(lptr0);
				fptr = lptr0;
				lptr0 = NULL;
			}
	
			if (len_fptr==0) continue;

			strncpy(fpath_buff, fptr, len_fptr);

			len = len_fptr;
			fpath_buff[len++] = '/';

			uls_strcpy(fpath_buff+len, fname);

			rc = uls_file_exist(fpath_buff);
			fpath_buff[len_fptr] = '\0';
			if (rc > 0) {
				err_log("\t%s ---> FOUND!", fpath_buff);
			} else {
				err_log("\t%s", fpath_buff);
			}
		}
	}
}
